﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Data;
using System.Collections.Specialized;
using System.Web;
using System.Xml;
using System.Collections;
using System.Reflection;

namespace NETXPF.Library
{
    /// <summary>
    /// Facilitates format conversion with easy-to-use robust utility methods.
    /// An extension of .NET's Convert class.
    /// </summary>
    public class ConvertEx
    {
        // The built in string to byte[] conversion methods for .NET are problematic in that they do not do an exact (same length) conversion.
        // System.Convert To/From base64string is designed for viewable/printable characters and does not handle other characters well.
        public static byte[] StringToBytes(string str)
        {
            var cnv = new Converter<char, byte>((c) => (byte)c);
            return Array.ConvertAll(str.ToCharArray(), cnv);
        }
        public static string BytesToString(byte[] bytes)
        {
            var result = "";
            foreach (var b in bytes)
                result += (char)b;
            return result;
        }

        public static string CSVtoJSON(Stream csvstream)
        {
            return JSON.JSONify(CSVAdapter.Read(csvstream));
        }

        /// <summary>
        /// Converts any list of objects to an array of arrays (a table) by selecting a specific subset of the object's properties as columns for the table.
        /// Shown in JSON, a sample object list would be converted as follows:
        /// -- Function Call: ObjectsToTable (objList, new string[]{"x", "y"});
        /// -- Original List: objList = [{x: 43.5, y: -79.4}, {x: 44.5, y: -80.4}];
        /// -- Output Table: tableData = [[43.5, -79.4], [44.5, -80.4]];
        /// </summary>
        /// <typeparam name="T">Object type</typeparam>
        /// <param name="list">List of objects to convert</param>
        /// <param name="propertyNames">Names of properties to extract as columns</param>
        /// <returns></returns>
        public static List<object[]> ObjectsToTable<T>(IEnumerable<T> list, string[] propertyNames)
        {
            List<object[]> propertyData = new List<object[]>();
            foreach (T item in list)
            {
                object[] columnData = new object[propertyNames.Length];
                int i = 0;
                foreach (string propertyName in propertyNames)
                {
                    object value = typeof(T).GetProperty(propertyName).GetValue(item, null);
                    columnData[i] = value;
                    i++;
                }
                propertyData.Add(columnData);
            }
            return propertyData;
        }

        /// <summary>
        /// Converts a DataTable to array of arrays format for serialization purposes.
        /// </summary>
        /// <param name="rows"></param>
        /// <returns></returns>
        public static List<object[]> DataTableToTable(DataRowCollection rows)
        {
            List<object[]> rowdata = new List<object[]>();
            foreach (DataRow r in rows)
            {
                rowdata.Add(r.ItemArray);
            }
            return rowdata;
        }

        /// <summary>
        /// Very useful for sending POST / GET requests
        /// </summary>
        /// <param name="nvc"></param>
        /// <returns></returns>
        public static string URLEncodeNVPs(NameValueCollection nvc)
        {
            StringBuilder nvpString = new StringBuilder();
            for (int i = 0; i < nvc.AllKeys.Length; i++)
            {
                if (i > 0)
                {
                    nvpString.Append("&");
                }
                string key = nvc.AllKeys[i];
                nvpString.Append(String.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(nvc[key])));
            }
            return nvpString.ToString();
        }

        /// <summary>
        /// For parsing query strings / POST data
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static NameValueCollection URLDecodeNVPs(string str)
        {
            NameValueCollection nvc = new NameValueCollection();
            foreach (string s in str.Split('&'))
            {
                int idx = s.IndexOf('=');
                if (idx >= 0)
                {
                    string left = s.Substring(0, idx);
                    string right = s.Substring(idx + 1);
                    nvc.Add(HttpUtility.UrlDecode(left), HttpUtility.UrlDecode(right));
                }
            }
            return nvc;
        }

        /// <summary>
        /// Useful for 'serializing' NVPs
        /// XML Structure:
        /// [object][field name='key1']value1[/field][field name='key2']value2[/field][/object]
        /// </summary>
        /// <param name="nvps"></param>
        /// <returns></returns>
        public static string NVPsToXMLFragment(NameValueCollection nvps)
        {
            StringBuilder sb = new StringBuilder();
            using (XmlTextWriter xw = new XmlTextWriter(new StringWriter(sb)))
            {
                xw.WriteStartDocument();
                xw.WriteStartElement("object");
                foreach (string key in nvps.AllKeys)
                {
                    xw.WriteStartElement("field");
                    xw.WriteAttributeString("name", key);
                    xw.WriteString(nvps[key]);
                    xw.WriteEndElement();
                }
                xw.WriteEndElement();
                xw.WriteEndDocument();
            }
            return sb.ToString();
        }

        /// <summary>
        /// Reads a special type of XML as Name-Value pairs
        /// XML Structure:
        /// [object][field name='key1']value1[/field][field name='key2']value2[/field][/object]
        /// </summary>
        /// <param name="xml"></param>
        /// <returns></returns>
        public static NameValueCollection NVPsFromXMLFragment(string xml)
        {
            NameValueCollection nvc = new NameValueCollection();
            using (XmlTextReader xr = new XmlTextReader(new StringReader(xml)))
            {
                xr.ReadStartElement("object");
                while (xr.IsStartElement("field"))
                {
                    string key = xr.GetAttribute("name");
                    string text = xr.ReadElementContentAsString();
                    nvc[key] = text;
                }
                xr.ReadEndElement();
            }
            return nvc;
        }

        public static NameValueCollection DictToNVPs<K, V>(Dictionary<K, V> dict)
        {
            NameValueCollection nvc = new NameValueCollection();
            foreach (K key in dict.Keys)
            {
                nvc[key.ToString()] = dict[key].ToString();
            }
            return nvc;
        }

        public static Hashtable DictToHashtable<K,V>(Dictionary<K, V> dict)
        {
            Hashtable ht = new Hashtable();
            foreach (K key in dict.Keys)
            {
                ht[key] = dict[key];
            }
            return ht;
        }

        public static NameValueCollection NVPsFromHashtable(Hashtable ht)
        {
            NameValueCollection nvc = new NameValueCollection();
            foreach (object key in ht.Keys)
            {
                nvc[key.ToString()] = ht[key].ToString();
            }
            return nvc;
        }

        public static string NVPsToJSON(NameValueCollection nvc)
        {
            return JSON.JSONify(nvc);
        }

        public static NameValueCollection NVPsFromJSON(string json)
        {
            Hashtable ht = (Hashtable)JSON.Parse(json);
            return NVPsFromHashtable(ht);
        }

        public static NameValueCollection ObjectToNVPs(string[] destKeys, string[] srcProperties, object o)
        {
            NameValueCollection nvc = new NameValueCollection();
            int i = 0;
            foreach(string property in srcProperties)
            {
                object value = o.GetType().GetProperty(property).GetValue(o, null);
                if (value != null)
                {
                    nvc[destKeys[i]] = value.ToString();
                }
                i++;
            }
            return nvc;
        }

        public static NameValueCollection ObjectToNVPs(object o)
        {
            NameValueCollection nvc = new NameValueCollection();
            foreach (PropertyInfo pi in o.GetType().GetProperties())
            {
                object value = o.GetType().GetProperty(pi.Name).GetValue(o, null);
                if (value != null)
                {
                    nvc[pi.Name] = value.ToString();
                }
            }
            return nvc;
        }

        public static T ObjectFromNVPs<T>(NameValueCollection nvc) where T : new()
        {
            T instance = new T();
            foreach (PropertyInfo pi in typeof(T).GetProperties())
            {
                pi.SetValue(instance, nvc[pi.Name], null);
            }
            return instance;
        }

        public static T ObjectFromNVPs<T>(string[] destProperties, string[] srcKeys, NameValueCollection nvc) where T : new()
        {
            T instance = new T();
            int i = 0;
            foreach (string property in destProperties)
            {
                typeof(T).GetProperty(property).SetValue(instance, nvc[srcKeys[i]], null);
                i++;
            }
            return instance;
        }

        public static List<KeyValuePair<string, string>> NVPsToEnumerable(NameValueCollection nvc)
        {
            List<KeyValuePair<string, string>> kvp = new List<KeyValuePair<string, string>>();
            foreach (string key in nvc.AllKeys)
            {
                kvp.Add(new KeyValuePair<string, string>(key, nvc[key]));
            }
            return kvp;
        }

        public static NameValueCollection EnumerableToNVPs(IEnumerable<KeyValuePair<string, string>> kvp)
        {
            NameValueCollection nvc = new NameValueCollection();
            foreach (KeyValuePair<string, string> kv in kvp)
            {
                nvc[kv.Key] = kv.Value;
            }
            return nvc;
        }

        public static Dictionary<K, V> ListToDictionary<K, V>(List<K> keys, List<V> values, Function<V, V, V> merge)
        {
            Dictionary<K, V> dict = new Dictionary<K, V>();
            for(int i = 0; i < keys.Count; i++)
            {
                if (!dict.ContainsKey(keys[i]))
                {
                    dict[keys[i]] = values[i];
                }
                else
                {
                    dict[keys[i]] = merge(dict[keys[i]], values[i]);
                }
            }
            return dict;
        }

        public static class MergeFunctions
        {
            public static List<T> ListMerge<T>(List<T> a, List<T> b)
            {
                a.AddRange(b);
                return a;
            }
        }
    }
}
